package com.spica.platform.base.async;

import com.spica.platform.base.context.LbIsolationContextHolder;
import com.spica.platform.base.context.SysUserContextHolder;
import com.spica.platform.base.context.TenantContextHolder;
import com.spica.platform.base.model.SysUser;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.Map;

/**
 * 包装线程任务,把希望在子线程中也能使用的数据塞回threadlocal中
 * 注意,这个线程更适合用在web请求中,因为涉及到请求参数,日志,安全,租户等配置信息,如果这些配置信息有为null的,
 * 比如MDC.getCopyOfContextMap()为null,再塞回去,会报错
 */
// https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor
// 传递RequestAttributes and MDC and SecurityContext
public class ContextCopyingDecorator implements TaskDecorator {
	/**
	 * 核心是把主线程中的数据,拷贝一份,在子线程中重新塞回去(注意:这里塞回去是塞回一个ThreadLocal中)
	 */
	@Override
	public Runnable decorate(Runnable runnable) {
		// 主线程
		//1.请求参数上下文
		RequestAttributes context = RequestContextHolder.currentRequestAttributes();
		//2.日志上下文
		Map<String, String> previous = MDC.getCopyOfContextMap();
		//3.安全上下文
		SecurityContext securityContext = SecurityContextHolder.getContext();
		//4.租户上下文
		String tenatId =  TenantContextHolder.getTenant();
		//5.用户上下文
		SysUser sysUser = SysUserContextHolder.getUser();
		//6.版本上下文
		String  version = LbIsolationContextHolder.getVersion();
		// 子线程
		return () -> {
			try {
				// 将变量重新放入到run线程中
				RequestContextHolder.setRequestAttributes(context);
				MDC.setContextMap(previous);
				SecurityContextHolder.setContext(securityContext);
				TenantContextHolder.setTenant(tenatId);
				SysUserContextHolder.setUser(sysUser);
				LbIsolationContextHolder.setVersion(version);
				runnable.run();
			} finally {
				RequestContextHolder.resetRequestAttributes();
				MDC.clear();
				SecurityContextHolder.clearContext();
				TenantContextHolder.clear();
				SysUserContextHolder.clear();
				LbIsolationContextHolder.clear();
			}
		};
	}
}
